Skip to content
【转载】Cesium VoxelPrimitive 实现体渲染

Cesium VoxelPrimitive 实现体渲染

Cesium 1.101 版本新增了 VoxelPrimitive 类和 VoxelProvider 接口,专门用于体素渲染,同时支持提体积渲染。

这项技术已经推出 2 年了,但目前官方文档中仍标记为实验特性(Experimental),也许是因为这样,国内才鲜有资料详细推广这项技术。本文结合代码详细介绍如何使用 VoxelPrimitiveVoxelProvider 和 CustomShader 实现体素渲染和体积渲染。

图片体积渲染在气象数据可视化中的应用

1、体素渲染与体积渲染

体素渲染(Voxel Rendering)是一种通过体素(Voxel,即体积像素)来进行图像渲染的技术。与传统的基于表面的渲染技术不同,体素渲染通过将三维空间分割成小的立方体单元来表示物体,这样可以更精确地捕捉复杂的内部结构和细节。

图片体素渲染效果(图由 AI 生成)

体积渲染(Volume Rendering)是一种在三维空间中显示物体内部结构的图像渲染技术。与体素渲染类似,体积渲染能够展示复杂的内部细节,但它不局限于立方体单元,而是能够渲染连续的三维数据。

图片天气雷达体积渲染效果

2、二三维格点数据

二维格点数据(2D Grid Data)是在二维空间中以规则网格形式排列的数据点。每个数据点对应一个特定的位置(通常是以行列表示)和属性值。这种数据形式广泛应用于科学计算、地理信息系统(GIS)、图像处理等领域。

图片二维格点数据可视化效果(全球二氧化碳浓度分布)

三维格点数据(3D Grid Data)是指在三维空间中以规则网格形式排列的数据点,每个数据点对应一个特定的位置和属性值。这种数据形式常用于科学计算、地质勘探、气象模拟等领域,可以高效地表示连续的三维分布信息。应用场景:

  • 气象模拟:用于表示大气中的各种参数,如温度、压力、湿度等。

  • 医学成像:如 CT 和 MRI 数据,以三维格点形式展示人体内部结构。

  • 地质勘探:用于表示地下矿藏分布、地形等信息。

  • 计算流体力学:用于模拟流体的流动状态,如空气动力学分析等。

图片三维格点数据可视化效果(二氧化碳浓度垂直分布)

3、Cesium 体渲染技术

我们先通过一张类图对 Cesium 体渲染技术有个总体的认识。

图片Cesium 体素渲染技术体系类图(精简版)

Cesium 体渲染技术体系主要由 VoxelPrimitive 类、 VoxelProvider 接口和一系列枚举类型组成,通过 Cesium 自定义着色器 CustomShader 实现体素数据(如三维格点数据)到颜色的映射。

3.1、体素数据源 VoxelProvider

我们需要自定义数据源类,实现体素数据源接口 VoxelProvider ,包括如下几个方面:

  • 通过 shape 属性定义整体形状;

  • 通过 dimensions 属性定义体素切片的数据维度;

  • 通过 namestypes 和 componentTypes 属性定义体素元数据;

  • 编写 requestData 方法,处理体素切片数据请求,返回体素数据。

3.1.1、整体形状定义

截至本文编写时间,Cesium 最新版为 1.123, 支持立方体、椭球体和圆柱体三种整体形状的体素渲染。从类图可以看出,属性 shape 可选值如下:

  • VoxelShapeType.BOX 整体形状为立方体,边界使用局部空间的直角坐标表示,通常适用于小范围的局部空间三维格点数据,需要注意的是,离中心位置越远的点,高度误差越大,当东西或者南北的距离超过50公里时请考虑使用椭球体;

  • VoxelShapeType.ELLIPSOID 整体形状为椭球体,边界使用球坐标表示,一般适用于空间范围较大的三维格点数据,同时这种形状的精度抖动较大,相机拉近到边缘时会出现明显的抖动;

  • VoxelShapeType.CYLINDER 整体形状为圆柱体,目前还不清楚具体应用场景。

图片体素渲染的3种整体形状效果图

3.1.2、体素切片定义

体素切片和二维影像切片类似,是把1个体块切割成8个大小相同体块,其中任一体块我们称之为体素切片。任一个体素切片包含的体素数量相同,这个数量由 dimensions 属性指定。

图片体素切片示意图(左:Level 1,右:Level 2)

图片体素切片的数据维度

Cesium 通过纹理将体素数据传入 GPU,受到纹理大小的限制,每个体素切片的体素数据量是有严格限制的。初步测试表明,在只有一个元数据,且元数据类型为VEC4、元数据分量类型为FLOAT32的情况下,1个体素切片的数据维度最大值为(157,157,157)。这个值仅供参考,实际应用中,其中 1 个维度大小调低、另外 2 个维度就可以适当调高。

3.1.2、体素元数据定义

体素元数据(Voxel Metadata)是指与体素(Voxel,即体积像素)相关的附加信息,这些信息用于描述体素的属性、状态或其他相关特征,例如颜色、透明度或者大气温度、二氧化碳浓度等等。

一个体素可以包含多个元数据,一个元数据包含名称、数据类型、数值 3 个方面的信息。由于体素渲染在 GPU 中完成,我们需要将数据通过更利于在 CPU 和 GPU 之间高效传输的方式进行组织。

VoxelProvider 通过namestypescomponentTypes定义体素元数据,这三个数组的长度必须一致。

  • names 元数据名称数组,这个名称在 fragmentShader 中通过 fsInput.metadata.<name>的方式获取,例如fsInput.metadata.color;,

  • types 元数据类型数组,例如颜色元数据类型为三维向量或四维向量,透明度为浮点型。这个属性在编写 CustomShader 的 fragmentShader 时候需要关注;

  • componentTypes 元数据分量类型,如颜色的四个分量可以转为0~255范围,然后使用UINT8类型传输,数据传输时交换的数据量相对小一些。这个属性在编写 requestData 方法时候需要关注。

3.1.3、体素切片数据请求

Cesium 内部实现了整套体素切片调度流程,自动根据相机距离决定需要渲染的 LOD 层级和体素切片,我们主要工作是实现VoxelProvider接口方法requestData,响应每个切片的数据请求,返回准备就绪的切片数据,如果返回undefined则不显示该切片,也不会继续进行细分。

返回的切片数据数组的第一个维度与元数据定义的names数组长度必须一致,第二个维度必须等于dimensions.x * dimensions.y * dimensions.z * channelCount

这里channelCount由元数据分量类型(componentTypes)决定,例如VEC4类型即四维向量的channelCount4VEC33SCALAR等为1,Cesium 提供了一个获取channelCount的方法Cesium.MetadataType.getComponentCount

一个简单示例:

class ProceduralMultiTileVoxelProvider {  constructor(shape) {    this.shape = shape;    this.dimensions = new Cesium.Cartesian3(4, 4, 4);    this.names = ["color", "alpha"];    this.types = [Cesium.MetadataType.VEC4, Cesium.MetadataType.SCALAR];    this.componentTypes = [      Cesium.MetadataComponentType.UINT8,      Cesium.MetadataComponentType.FLOAT32,    ];    this._levelCount = 2;  }  requestData(options) {    const { tileLevel, tileX, tileY, tileZ } = options;    if (tileLevel >= this._levelCount) {      return undefined;    }    const dimensions = this.dimensions;    //元数据:color    const colorMetadataType = this.types[0];    const voxelCount = dimensions.x * dimensions.y * dimensions.z;    const colorChannelCount =      Cesium.MetadataType.getComponentCount(colorMetadataType);    const dataColor = new Uint8Array(voxelCount * colorChannelCount).fill(1);    //元数据:alpha    const alphaMetadataType = this.types[1];    const alphaChannelCount =      Cesium.MetadataType.getComponentCount(alphaMetadataType);    const dataAlpha = new Float32Array(voxelCount * alphaChannelCount).fill(1);    //按照 names 数组的顺序排列    return Promise.resolve([dataColor, dataAlpha]);  }}

3.2、体素着色 CustomShader

数据源类已经定义了每个体素的元数据,接下来使用 Cesium 自定义着色器接收体素数据,并转为颜色和透明度,通过 czm_modelMaterial 类型的材质对象的diffusealpha字段传入 Cesium 内置的 RayMarching 算法流程,完成最终的体素数据渲染。

const customShader = new Cesium.CustomShader({  fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)    {        vec3 cellColor = fsInput.metadata.color.rgb; //获取元数据 color        vec3 cellAlpha = fsInput.metadata.color.alpha; //获取元数据 alpha        //TODO: 如果元数据不是颜色,则在这里继续实现从获取元数据到颜色的转换处理        material.diffuse = cellColor;        material.alpha = cellAlpha;    }`,});

这个示例运行结果是所有体素都是白色,没有任何实际用处,仅展示数据的从VoxelProviderCustomShader的整体处理流程。

3.3、体素图元 VoxelPrimitive

前面准备工作完成,接下来就比较容易了。创建 VoxelPrimitive,传入 providercustomShadermodelMatrix,然后添加到viewer.scene.primitives即可。

const provider = new ProceduralMultiTileVoxelProvider(  Cesium.VoxelShapeType.BOX);const voxelPrimitive = scene.primitives.add(  new Cesium.VoxelPrimitive({    provider: provider,    customShader: customShader,    modelMatrix: modelMatrix,  }));

VoxelPrimitive重要参数有:

  • modelMatrix 空间变换矩阵,用于空间定位和调整整体大小等;

  • screenSpaceError LOD 参数,用于调整同一视角下,LOD 的细分程度,值越大细分越粗糙,性能越高,反正越精细,性能越低;

  • nearestSampling 用于切换插值方法,值为 true 则启用邻近插值法,false 则使用线性插值。

  • stepSize 光线步进步长。步长越大,画质越低,性能越高,反之画质越高,性能越低。

3.3.1、空间变换 modelMatrix

无论形状是椭球、立方体,还是圆柱体,Cesium 内部都将整体视为顶点坐标在[-1,1]范围内的几何体,坐标系在几何体的中心。弄清楚这点,我们才能准确地构造空间变换矩阵,将体素按照指定缩放倍数渲染到目标空间范围内。

图片

空间变换-局部参考系(以立方体形状为例)
示例一

const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(  Cesium.Cartesian3.fromDegrees(106.642372689378, 26.623450331223));Cesium.Matrix4.multiplyByTranslation(  modelMatrix,  new Cesium.Cartesian3(0, 0, 1000),  modelMatrix);const scaleMatrix = Cesium.Matrix4.fromScale(  new Cesium.Cartesian3(1000, 1000, 1000));Cesium.Matrix4.multiply(modelMatrix, scaleMatrix, modelMatrix);Cesium.Matrix4.multiplyByTranslation(  modelMatrix,  new Cesium.Cartesian3(0, 0, 1),  modelMatrix);

示例二

const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(  Cesium.Cartesian3.fromDegrees(106.642372689378, 26.623450331223));const localTransformParams = new Cesium.TranslationRotationScale(  new Cesium.Cartesian3(0, 0, 1000),  null,  new Cesium.Cartesian3(1000, 1000, 1000));const localTransform =  Cesium.Matrix4.fromTranslationRotationScale(localTransformParams);Cesium.Matrix4.multiply(modelMatrix, localTransform, modelMatrix);

图片体素图元空间变换

3.3.2、LOD 参数 screenSpaceError

图片同一视角下两个 screenSpaceError 值效果对比

3.3.3、插值方法 nearestSampling

图片两种插值方法对比(左:邻近插值,右:线性插值)

4、Cesium 体渲染应用

三维体素数据集通常比较大,Cesium 体渲染通过 LOD(Level of Detail,细节层次),支持大量数据,提高渲染性能。要真正应用这项技术,必须具备 LOD 的数据处理思维,能够结合实际三维格点数据特点,设计出高效的格点数据切片和采样程序。

图片体积渲染在气象数据可视化中的应用

本文转自 https://mp.weixin.qq.com/s/IbDcjLL-A35ImOVa2QlZ7A,如有侵权,请联系删除。

Updated at: